package com.atguigu.gulimall.product.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.atguigu.common.constant.ProductConstant;
import com.atguigu.common.to.SkuEsModel;
import com.atguigu.common.to.SkuHasStockTo;
import com.atguigu.common.to.SkuReductionTo;
import com.atguigu.common.to.SpuBoundsTo;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;
import com.atguigu.common.utils.R;
import com.atguigu.gulimall.product.dao.SpuInfoDao;
import com.atguigu.gulimall.product.entity.*;
import com.atguigu.gulimall.product.feign.CouponFeignService;
import com.atguigu.gulimall.product.feign.SearchFeignService;
import com.atguigu.gulimall.product.feign.WareFeignService;
import com.atguigu.gulimall.product.service.*;
import com.atguigu.gulimall.product.vo.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;


@Service("spuInfoService")
public class SpuInfoServiceImpl extends ServiceImpl<SpuInfoDao, SpuInfoEntity> implements SpuInfoService {

    @Resource
    private SpuInfoDescService spuInfoDescService;
    @Resource
    private SpuImagesService spuImagesService;
    @Resource
    private AttrService attrService;
    @Resource
    private ProductAttrValueService productAttrValueService;
    @Resource
    private SkuInfoService skuInfoService;
    @Resource
    private SkuImagesService skuImagesService;
    @Resource
    private SkuSaleAttrValueService skuSaleAttrValueService;
    @Resource
    private CouponFeignService couponFeignService;
    @Resource
    private BrandService brandService;
    @Resource
    private CategoryService categoryService;
    @Resource
    private WareFeignService wareFeignService;
    @Resource
    private SearchFeignService searchFeignService;

    @Transactional
    @Override
    public void saveSpuInfo(SpuSaveVo vo) {
        // 1. 保存 SPU 基本信息 pms_spu_info
        SpuInfoEntity spuInfoEntity = new SpuInfoEntity();
        BeanUtils.copyProperties(vo, spuInfoEntity);
        spuInfoEntity.setCreateTime(new Date());
        spuInfoEntity.setUpdateTime(new Date());
        this.save(spuInfoEntity);

        // 2. 保存 SPU 的描述信息 pms_spu_info_desc
        // 因为 SPU 的描述信息可能是一个长文本，所以单独建表
        SpuInfoDescEntity spuInfoDescEntity = new SpuInfoDescEntity();
        spuInfoDescEntity.setSpuId(spuInfoEntity.getId());
        spuInfoDescEntity.setDecript(String.join(",", vo.getDecript()));
        spuInfoDescService.save(spuInfoDescEntity);

        // 3. 保存 SPU 图片集 pms_spu_images
        List<String> spuImages = vo.getImages();
        spuImagesService.saveImages(spuInfoEntity.getId(), spuImages);

        // 4. 保存 SPU 的规格参数信息 pms_product_attr_value
        List<BaseAttrs> baseAttrs = vo.getBaseAttrs();
        List<ProductAttrValueEntity> collect = baseAttrs.stream().map(attr -> {
            ProductAttrValueEntity productAttrValue = new ProductAttrValueEntity();
            productAttrValue.setAttrId(attr.getAttrId());
            // pms_product_attr_value 设计了属性名作为冗余字段，需要查询填充
            AttrEntity attrEntity = attrService.getById(attr.getAttrId());
            productAttrValue.setAttrName(attrEntity.getAttrName());
            productAttrValue.setAttrValue(attr.getAttrValues());
            productAttrValue.setQuickShow(attr.getShowDesc());
            productAttrValue.setSpuId(spuInfoEntity.getId());
            return productAttrValue;
        }).collect(Collectors.toList());
        productAttrValueService.saveBatch(collect);

        // 5. 保存 SPU 积分信息 gulimall-coupon 数据库下的 sms_spu_bounds
        if (ObjectUtil.isNotNull(vo.getBounds())) {
            SpuBoundsTo spuBoundsTo = new SpuBoundsTo();
            BeanUtils.copyProperties(vo.getBounds(), spuBoundsTo);
            spuBoundsTo.setSpuId(spuInfoEntity.getId());
            couponFeignService.saveSpuBounds(spuBoundsTo);
        }

        // 6. 保存当前 SPU 对应所有 SKU 信息
        // 6.1 保存 SKU 基本信息 pms_sku_info
        List<Skus> skus = vo.getSkus();
        if (CollectionUtil.isNotEmpty(skus)) {
            skus.forEach(sku -> {
                String skuDefaultImg = sku.getImages().stream().filter(img -> img.getDefaultImg() == 1).map(Images::getImgUrl).findFirst().get();

                SkuInfoEntity skuInfoEntity = new SkuInfoEntity();
                BeanUtils.copyProperties(sku, skuInfoEntity);
                skuInfoEntity.setSpuId(spuInfoEntity.getId());
                // skuInfoEntity.setSkuDesc();
                skuInfoEntity.setCatalogId(vo.getCatalogId());
                skuInfoEntity.setBrandId(vo.getBrandId());
                skuInfoEntity.setSkuDefaultImg(skuDefaultImg);
                skuInfoEntity.setSaleCount(0L);

                skuInfoService.save(skuInfoEntity);

                Long skuId = skuInfoEntity.getSkuId();

                // 6.2 保存 SKU 的图片信息 pms_sku_images
                List<SkuImagesEntity> skuImageList = sku.getImages().stream().map(img -> {
                            SkuImagesEntity skuImagesEntity = new SkuImagesEntity();
                            skuImagesEntity.setSkuId(skuId);
                            skuImagesEntity.setImgUrl(img.getImgUrl());
                            skuImagesEntity.setDefaultImg(img.getDefaultImg());
                            return skuImagesEntity;
                        })
                        // 过滤掉没有图片路径的图片
                        .filter(img -> StringUtils.hasText(img.getImgUrl()))
                        .collect(Collectors.toList());
                skuImagesService.saveBatch(skuImageList);

                // 6.3 保存 SKU 的销售属性信息 pms_sku_sale_attr_value
                List<SkuSaleAttrValueEntity> skuSaleAttrValueList = sku.getAttr().stream().map(attr -> {
                    SkuSaleAttrValueEntity skuSaleAttrValueEntity = new SkuSaleAttrValueEntity();
                    BeanUtils.copyProperties(attr, skuSaleAttrValueEntity);
                    skuSaleAttrValueEntity.setSkuId(skuId);
                    return skuSaleAttrValueEntity;
                }).collect(Collectors.toList());
                skuSaleAttrValueService.saveBatch(skuSaleAttrValueList);

                // 7. 保存 SKU 的打折、满减、会员价格等信息 gulimall-coupon 数据库下的 sms_sku_ladder、sms_sku_full_reduction、sms_member_price
                SkuReductionTo skuReductionTo = new SkuReductionTo();
                BeanUtils.copyProperties(sku, skuReductionTo);
                skuReductionTo.setSkuId(skuId);
                if (skuReductionTo.getFullCount() > 0 || skuReductionTo.getFullPrice().compareTo(new BigDecimal("0")) == 1) {
                    couponFeignService.saveSkuReduction(skuReductionTo);
                }
            });
        }

    }

    @Override
    public PageUtils queryPageByCondition(Map<String, Object> params) {

        LambdaQueryWrapper<SpuInfoEntity> wrapper = new LambdaQueryWrapper<>();

        String key = (String) params.get("key");
        wrapper.and(StringUtils.hasText(key), w -> w.eq(SpuInfoEntity::getId, key).or().like(SpuInfoEntity::getSpuName, key));

        String status = (String) params.get("status");
        wrapper.eq(StringUtils.hasText(status), SpuInfoEntity::getPublishStatus, status);

        String brandId = (String) params.get("brandId");
        // 如果 brandId 不为空，并且 brandId 不等于 0，则查询对应的品牌信息
        // 项目约定：brandId 等于 0 表示查询所有品牌，不需要拼接查询条件
        wrapper.eq(StringUtils.hasText(brandId) && !"0".equalsIgnoreCase(brandId), SpuInfoEntity::getBrandId, brandId);

        String catalogId = (String) params.get("catalogId");
        // 如果 catalogId 不为空，并且 catalogId 不等于 0，则查询对应的品牌信息
        // 项目约定：catalogId 等于 0 表示查询所有分类，不需要拼接查询条件
        wrapper.eq(StringUtils.hasText(catalogId) && !"0".equalsIgnoreCase(catalogId), SpuInfoEntity::getCatalogId, catalogId);

        IPage<SpuInfoEntity> page = this.page(new Query<SpuInfoEntity>().getPage(params), wrapper);

        return new PageUtils(page);
    }

    /**
     * 商品上架
     * 根据 spuId 查询当前 SPU 下所有 SKU 信息、品牌信息、分类信息，封装成 SkuEsModel 集合，并批量保存到 ElasticSearch 中
     *
     * @param spuId
     */
    @Override
    public void up(Long spuId) {

        // 同一个 SPU 有多个 SKU
        List<SkuInfoEntity> skus = skuInfoService.getSkusBySpuId(spuId);

        // 发送远程调用，库存系统判断当前 SKU 是否有库存
        // 避免封装每个 SKU 数据时都调用库存系统判断是否有库存，最好在循环外定义一个批量查询多个 SKU 是否有库存的方法，减少远程调用次数
        List<Long> skuIdList = skus.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList());
        Map<Long, Boolean> skuHasStockMap = new HashMap<>();
        try {
            R r = wareFeignService.getSkusHasStock(skuIdList);
            // 获取库存系统返回的数据
            Object object = r.get("data");
            // 转为 JSON 字符串，这里实际上是一个 JSONArray，需要转为 List<SkuHasStockTo>
            String jsonStr = JSONUtil.toJsonStr(object);
            List<SkuHasStockTo> data = JSONUtil.toList(jsonStr, SkuHasStockTo.class);
            skuHasStockMap = data.stream().collect(Collectors.toMap(SkuHasStockTo::getSkuId, SkuHasStockTo::getHasStock));
        } catch (Exception e) {
            log.error("库存系统查询失败，原因：{}", e);
        }

        // 查询当前 SKU 所对应 SPU 所有可以被检索的规格参数
        // SKU 的规格参数在其 SPU 中维护，同一个 SPU 下的所有 SKU 都有同样的规格参数
        // 规格参数的 search_type 字段决定该参数是否参与检索：0-不需要被检索，1-需要被检索
        // 根据 spuId 查询出当前 SPU 所有规格参数
        List<ProductAttrValueEntityVo> baseAttrListForSpu = productAttrValueService.baseAttrListForSpu(spuId);
        // 获取当前 SPU 所有规格参数的 attrId
        List<Long> baseAttrIdListForSpu = baseAttrListForSpu.stream().map(ProductAttrValueEntity::getAttrId).collect(Collectors.toList());
        // 查询出当前 SPU 所有规格参数中，规格参数是需要被检索的参数 ID
        List<Long> searchAttrIdListForSpu = attrService.selectSearchAttrs(baseAttrIdListForSpu);
        // 从当前 SPU 所有规格参数中，过滤出需要被检索的规格参数
        // 将 searchAttrIdListForSpu 集合转为 Set，方便过滤操作中判断规格参数ID是否存在于 searchAttrIdSetForSpu
        Set<Long> searchAttrIdSetForSpu = new HashSet<>(searchAttrIdListForSpu);
        List<SkuEsModel.Attrs> attrsList = baseAttrListForSpu.stream()
                // 过滤出需要被检索的规格参数
                .filter(item -> searchAttrIdSetForSpu.contains(item.getAttrId()))
                // 封装规格参数为 SkuEsModel.Attrs
                .map(item -> {
                    SkuEsModel.Attrs attrs = new SkuEsModel.Attrs();
                    BeanUtils.copyProperties(item, attrs);
                    return attrs;
                })
                .collect(Collectors.toList());


        // 在 lambda 表达式中使用的局部变量必须是 final 关键字修饰或者“实际上不可变的”
        Map<Long, Boolean> finalSkuHasStockMap = skuHasStockMap;
        // 封装 SKU 信息到 SkuEsModel
        List<SkuEsModel> upSkus = skus.stream().map(sku -> {
            SkuEsModel skuEsModel = new SkuEsModel();
            BeanUtils.copyProperties(sku, skuEsModel);
            skuEsModel.setSkuPrice(sku.getPrice());
            skuEsModel.setSkuImg(sku.getSkuDefaultImg());
            // 设置当前 SKU 是否有库存
            if (CollectionUtil.isEmpty(finalSkuHasStockMap)) {
                skuEsModel.setHasStock(true);
            } else  {
                skuEsModel.setHasStock(finalSkuHasStockMap.get(sku.getSkuId()));
            }
            // 热度评分默认为 0，但实际上往往商家都会发挥钞能力，置顶新上架商品
            skuEsModel.setHotScore(0L);
            // 查询品牌信息
            BrandEntity brandEntity = brandService.getById(sku.getBrandId());
            skuEsModel.setBrandName(brandEntity.getName());
            skuEsModel.setBrandImg(brandEntity.getLogo());
            // 查询分类信息
            CategoryEntity categoryEntity = categoryService.getById(sku.getCatalogId());
            skuEsModel.setCatalogName(categoryEntity.getName());
            // 设置 SPU 所有可被检索的规格参数
            skuEsModel.setAttrs(attrsList);
            return skuEsModel;
        }).collect(Collectors.toList());

        // 远程调用 search 微服务，批量添加商品文档到 ES 中的商品索引
        R r = searchFeignService.productStatusToUp(upSkus);
        if (r.getCode() == 0) {
            // 远程调用成功，商品上架成功，修改 pms_spu_info 表中商品上架状态为 1
            this.update(new LambdaUpdateWrapper<SpuInfoEntity>()
                    .eq(SpuInfoEntity::getId, spuId)
                    .set(SpuInfoEntity::getPublishStatus, ProductConstant.StatusEnum.UP_SPU.getCode())
                    .set(SpuInfoEntity::getUpdateTime, new Date()));
        } else {
            // 远程调用失败，商品上架失败
            // TODO 重试机制，接口幂等性处理
            log.error("商品上架失败");
        }
    }

    @Override
    public SpuInfoEntity getSpuInfoBySkuId(Long skuId) {
        SkuInfoEntity skuInfoEntity = skuInfoService.getById(skuId);
        return this.getById(skuInfoEntity.getSpuId());
    }

}